package uk.co.accolade.clash.app;

import uk.co.accolade.clash.core.*;
import uk.co.accolade.clash.util.Each;
import static uk.co.accolade.clash.util.Each.each;

import javax.swing.*;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class ClashTree implements ClashHandler {

    private DefaultTreeModel treeModel;
    private MutableTreeNode rootNode;
    private JTree tree;
   
    public static ClashTree createTree( TreeSelectionListener listener, DefaultTreeCellRenderer renderer ) {
        MutableTreeNode rootNode = new DefaultMutableTreeNode( "Clashes" );
        DefaultTreeModel treeModel = new DefaultTreeModel( rootNode );
        return new ClashTree( listener, treeModel, rootNode, renderer );
    }

    public JTree getTree() {
        return tree;
    }

    public ClashTree( TreeSelectionListener listener, DefaultTreeModel treeModel, MutableTreeNode rootNode, DefaultTreeCellRenderer renderer ) {
        this.rootNode = rootNode;
        this.treeModel = treeModel;
        tree = new JTree( treeModel );
        tree.addTreeSelectionListener( listener );
        tree.setShowsRootHandles( true );
        tree.setCellRenderer( renderer );
    }

    public void handleClashes( UserChanges clashedChanges ) {
        final MutableTreeNode userNode = getOrCreateUserNode( clashedChanges.getUsername() );
        for( ClashFilePath filePath : clashedChanges.getChangedFilesReadOnly() ) {
            addFileNode( filePath, userNode, treeModel );
        }
        expandUserNode( userNode );
    }

    public void clearClashes( UserChanges clashedChanges ) {
        MutableTreeNode userNode = findUserNode(clashedChanges.getUsername());
        if( userNode != null ) {
            for( ClashFilePath filePath : clashedChanges.getChangedFilesReadOnly() ) {
                MutableTreeNode fileNode = findFileNode( filePath, userNode );
                if( fileNode != null ) {
                    treeModel.removeNodeFromParent( fileNode );
                }
            }
        }
    }

    private void expandUserNode( MutableTreeNode userNode ) {
        tree.expandPath( new TreePath( new Object[]{rootNode, userNode} ) );
    }

    @SuppressWarnings( "unchecked" )
    private List<MutableTreeNode> getDetachedChildren( MutableTreeNode parentNode ) {
        final List<MutableTreeNode> detachedChildren = new ArrayList<MutableTreeNode>();
        each( parentNode.children(), new Each<MutableTreeNode>() {
            public void iterate( MutableTreeNode node ) {
                detachedChildren.add( node );
            }
        } );
        return detachedChildren;
    }

    private MutableTreeNode getOrCreateUserNode( Username username ) {
        MutableTreeNode userNode = findUserNode(username);
        return ( userNode != null ) ? userNode : addNewNode( new UserNode(username), rootNode, treeModel );
    }

    private MutableTreeNode addFileNode( ClashFilePath filePath, MutableTreeNode userNode, DefaultTreeModel treeModel ) {
        MutableTreeNode fileNode = findFileNode( filePath, userNode );
        if( fileNode == null ) {
            fileNode = addNewNode( new FileNode( filePath ), userNode, treeModel );
        }
        return fileNode;
    }

    private MutableTreeNode findFileNode(ClashFilePath filePath, MutableTreeNode parent) {
        return findMatchingChild( filePath.getFilePath(), parent );
    }

    private MutableTreeNode findUserNode(Username username) {
        return findMatchingChild( username.getUsername(), rootNode);
    }

    private MutableTreeNode findMatchingChild( final String nodeText, MutableTreeNode parent ) {
        Enumeration children = parent.children();
        while( children.hasMoreElements() ) {
            MutableTreeNode child = (MutableTreeNode) children.nextElement();
            if( nodeText.equals( child.toString() ) ) {
                return child;
            }
        }
        return null;
    }

    private DefaultMutableTreeNode addNewNode( Object nodeData, MutableTreeNode parent, DefaultTreeModel treeModel ) {
        DefaultMutableTreeNode userNode = new DefaultMutableTreeNode( nodeData, true );
        treeModel.insertNodeInto( userNode, parent, parent.getChildCount() );
        return userNode;
    }
}